/*	
	
	Copyright (c) 2007-2009 Ryan Christiansen
	
	This software is provided 'as-is', without any express or implied
	warranty. In no event will the authors be held liable for any damages
	arising from the use of this software.
	
	Permission is granted to anyone to use this software for any purpose,
	including commercial applications, and to alter it and redistribute it
	freely, subject to the following restrictions:
	
	1. The origin of this software must not be misrepresented; you must not
	claim that you wrote the original software. If you use this software
	in a product, an acknowledgment in the product documentation would be
	appreciated but is not required.
	
	2. Altered source versions must be plainly marked as such, and must not be
	misrepresented as being the original software.
	
	3. This notice may not be removed or altered from any source
	distribution.
	
*/
package funit.core
{
	import flash.utils.getTimer;
	
	import funit.errors.AssertionError;
	import funit.errors.IgnoreError;
	import funit.resources.AssertResources;
	
	import sv.reflection.MethodInfo;
	import sv.utils.ClassUtil;
	import sv.utils.ErrorUtil;
	
	public class TestMethod extends TestCase
	{
		private var _method:MethodInfo = null;
		
		private var _methodSetUp:MethodInfo = null;
		private var _methodTearDown:MethodInfo = null;
		
		private var _errorExpected:Boolean = false;
		private var _expectedErrorName:String = null;
		private var _expectedErrorType:Class = null;
		private var _expectedMessage:String = null;
		private var _errorHandler:MethodInfo = null;
		
		
		/**
		 * @param method
		 */
		public function TestMethod( method:MethodInfo )
		{
			super(method.reflectedType);
			
			this.testName.name = (method.declaringType.fullName == method.reflectedType.fullName)
				? method.name : method.declaringType.name + "." + method.name;
			this.testName.fullName = method.reflectedType.fullName + "." + method.name;
			
			_method = method;
			
			_methodSetUp = FUnitFramework.getSetUpMethod(this.fixtureType);
			_methodTearDown = FUnitFramework.getTearDownMethod(this.fixtureType);
		}
		
		
		/**
		 * @param testResult
		 */
		override protected function runTestCase( testResult:TestResult ) : void
		{
			try
			{
				if (!testResult.isFailure)
				{
					// This supports tests that execute a TestCase directly.
					if (fixture == null)
					{
						this.fixture = fixtureType.createInstance();
					}
					
					runTestCaseImpl(testResult);
				}
			}
			catch (e:Error)
			{
				recordError(e, testResult);
			}
		}
		
		/**
		 * @param testResult
		 */
		private function runTestCaseImpl( testResult:TestResult ) : void
		{
			testResult.startTime = getTimer();
			
			try
			{
				if (methodSetUp != null)
				{
					methodSetUp.invoke(this.fixture);
				}
				
				invokeTestCase(testResult);
			}
			catch (e:Error)
			{
				recordError(e, testResult);
			}
			finally 
			{
				runTearDown(testResult);
				
				var stopTime:Number = getTimer();
				testResult.time =  stopTime - testResult.startTime;
			}
		}
		
		/**
		 * @param testResult
		 */
		private function invokeTestCase( testResult:TestResult ) : void
		{
			try
			{
				method.invoke(this.fixture);
				processNoException(testResult);
			}
			catch (e:Error)
			{
				if (e is IgnoreError)
				{
					testResult.ignore(e.message);
				}
				else
				{
					processException(testResult, e);
				}
			}
		}
		
		/**
		 * @param testResult
		 */
		private function runTearDown( testResult:TestResult ) : void
		{
			try
			{
				if (methodTearDown != null)
				{
					methodTearDown.invoke(this.fixture);
				}
			}
			catch (e:Error)
			{
				if (e is AssertionError)
				{
					testResult.failure(e.message, ErrorUtil.getStackTrace(e), FailureSite.TearDown);
				}
				else
				{
					testResult.error(e, FailureSite.TearDown);
				}
			}
		}
		
		/**
		 * @param testResult
		 * @param error
		 */
		protected function processException( testResult:TestResult, error:Error ) : void
		{
			if (!_errorExpected)
			{
				recordError(error, testResult);
			}
			else
			{
				if (error is _expectedErrorType)
				{
					if (_expectedMessage == null || error.message == _expectedMessage)
					{
						if (_errorHandler != null)
						{
							_errorHandler.invoke(this.fixture, error);
						}
						
						testResult.success();
					}
					else
					{
						testResult.failure(
							AssertResources.getString(
								"Errors.AssertFailedError.UnexpectedErrorMessage",
								_expectedMessage
							),
							ErrorUtil.getStackTrace(error)
						);
					}
				}
				else if (error is AssertionError)
				{
					testResult.failure(
						error.message,
						ErrorUtil.getStackTrace(error)
					);
				}
				else
				{
					testResult.failure(
						AssertResources.getString(
							"Errors.AssertFailedError.UnexpectedErrorType",
							ClassUtil.getQualifiedClassName(error),
							_expectedErrorName
						),
						ErrorUtil.getStackTrace(error)
					);
				}
			}
		}
		
		/**
		 * @param testResult
		 */
		protected function processNoException( testResult:TestResult ) : void
		{
			if (_errorExpected)
			{
				testResult.failure(
					AssertResources.getString(
						"Errors.AssertFailedError.ErrorTypeExpected",
						_expectedErrorName
					),
					null
				);
			}
			else
			{
				testResult.success();
			}
		}
		
		/**
		 * @param error
		 * @param testResult
		 */
		protected function recordError( error:Error, testResult:TestResult ) : void
		{
			if (error is IgnoreError)
			{
				testResult.ignore(error.message);
			}
			else if (error is AssertionError)
			{
				testResult.failure(
					error.message,
					ErrorUtil.getStackTrace(error)
				);
			}
			else
			{
				testResult.error(error);
			}
		}
		
		
		/**
		 * 
		 */
		public function get methodSetUp() : MethodInfo
		{
			return _methodSetUp;
		}
		
		public function set methodSetUp( value:MethodInfo ) : void
		{
			_methodSetUp = value;
		}
		
		
		/**
		 * 
		 */
		public function get methodTearDown() : MethodInfo
		{
			return _methodTearDown;
		}
		
		public function set methodTearDown( value:MethodInfo ) : void
		{
			_methodTearDown = value;
		}
		
		
		/**
		 * 
		 */
		public function get method() : MethodInfo
		{
			return _method;
		}
		
		public function set method( value:MethodInfo ) : void
		{
			_method = value;
		}
		
		
		/**
		 * 
		 */
		public function get errorExpected() : Boolean
		{
			return _errorExpected;
		}
		
		public function set errorExpected( value:Boolean ) : void
		{
			_errorExpected = value;
		}
		
		
		/**
		 * 
		 */
		public function get expectedErrorName() : String
		{
			return _expectedErrorName;
		}
		
		public function set expectedErrorName( value:String ) : void
		{
			_expectedErrorName = value;
		}
		
		
		/**
		 * 
		 */
		public function get expectedErrorType() : Class
		{
			return _expectedErrorType;
		}
		
		public function set expectedErrorType( value:Class ) : void
		{
			_expectedErrorType = value;
		}
		
		
		/**
		 * 
		 */
		public function get expectedMessage() : String
		{
			return _expectedMessage;
		}
		
		public function set expectedMessage( value:String ) : void
		{
			_expectedMessage = value;
		}
		
		
		/**
		 * 
		 */
		public function get errorHandler() : MethodInfo
		{
			return _errorHandler;
		}
		
		public function set errorHandler( value:MethodInfo ) : void
		{
			_errorHandler = value;
		}
		
		
		/**
		 * 
		 */
		override public function get testType() : String
		{
			return "Test Method";
		}
		
	}
	
}
